package com.soho.core;

import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import com.jfinal.kit.JsonKit;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.soho.helper.PrintHelper;
import com.soho.helper.SqlHelper;
import com.soho.helper.StatusHelper;
import com.soho.helper.TimeHelper;

import online.mtapi.mt4.Bar;
import online.mtapi.mt4.Op;
import online.mtapi.mt4.Order;
import online.mtapi.mt4.OrderClient;
import online.mtapi.mt4.OrderProgressEventArgs;
import online.mtapi.mt4.OrderProgressEventHandler;
import online.mtapi.mt4.OrderUpdateEventArgs;
import online.mtapi.mt4.OrderUpdateEventHandler;
import online.mtapi.mt4.QuoteClient;
import online.mtapi.mt4.QuoteEventArgs;
import online.mtapi.mt4.QuoteEventHandler;
import online.mtapi.mt4.QuoteHistoryEventArgs;
import online.mtapi.mt4.QuoteHistoryEventHandler;
import online.mtapi.mt4.ThreadPool;
import online.mtapi.mt4.Timeframe;
import online.mtapi.mt4.Exception.ConnectException;
import online.mtapi.mt4.Exception.TimeoutException;
import online.mtapi.mt4.srv.PrimaryServer;
import online.mtapi.mt4.srv.SecondaryServer;
import online.mtapi.mt4.srv.Servers;

public class Mt4 {

	private static Mt4 instance = new Mt4();
	private static QuoteClient qc = null;

	private Mt4() {

	}

	public synchronized static Mt4 getInstance() {
		if (qc == null) {
			qc = instance.getClient();
		}
		return instance;
	}

	public QuoteClient getClient() {
		if (qc == null) {
			qc = instance.connectServer();
		}
		if (qc == null) {
			System.out.println("fails to server");
			System.exit(0);
		}
		if (!qc.Connected()) {
			qc = instance.connectServer();
		}
		return qc;
	}

	// =====DB Connect Start
	private QuoteClient connectServer() {
		try {
			String srvPath = Paths.get("AxiTrader.srv").toAbsolutePath().toString();
			Servers servers = QuoteClient.LoadSrv(srvPath);

			QuoteClient qc = connect(servers.PrimaryServer, servers.SecondaryServers, 8488331, "5zhilwx");
			System.out.println("Connected to server "+"Account balance= " + qc.AccountBalance());
			return qc;
		} catch (Exception ex) {
			System.exit(0);
			System.out.println(ex.getMessage());
		}
		return null;
	}

	private QuoteClient connect(PrimaryServer primary, SecondaryServer[] slaves, int user, String password)
			throws IOException {
		System.out.println("Connecting... host="+primary.getHost()+", port = "+primary.getPort());
		QuoteClient qc = new QuoteClient(user, password, primary.getHost(), primary.getPort());
		try {
			qc.Connect();
			return qc;
		} catch (Exception ex) {
			System.out.println("Cannot connect to primary server: " + ex.getMessage());
			return connectSlaves(slaves, user, password);
		}
	}

	private QuoteClient connectSlaves(SecondaryServer[] servers, int user, String password) throws IOException {
		System.out.println("Connecting to slaves...");
		for (SecondaryServer server : servers) {
			QuoteClient qc = new QuoteClient(user, password, server.getHost(), server.getPort());
			try {
				qc.Connect();
				return qc;
			} catch (Exception ex) {
				System.out.println(ex.getMessage());
			}
		}
		throw new RuntimeException("Cannot connect to secondary servers");
	}
	// =====DB Connect End

	private void print(Object object) {
		PrintHelper.print(object.toString());
	}

	// ======subscribe start
	public void subscribe(String symbol) {
		PrintHelper.print("Subscribe " + symbol);
		getClient().Subscribe(symbol);
	}
	
	public void unSubscribe(String symbol) {
		PrintHelper.print("UnSubscribe " + symbol);
		getClient().Unsubscribe(symbol);
	}

	public void listenSymbol() {
		getClient().OnQuote.addListener(new QuoteEventHandler() {
			@Override
			public void invoke(Object sender, QuoteEventArgs args) {
				// 1:写数据库,2:推送出去
				// new
				// Price().setAsk(args.Ask).setBid(args.Bid).setTime(args.Time).setSymbol(args.Symbol).save();

				print("sender = " + sender + " and args = " + args);
			}

		});
	}
	// ======subscribe end

	// ====== open market order start 以市场价买入或卖出操作
	public Order openMarket(String symbol, Op op, double volume, double price) {
		return openMarket(symbol, op, volume, price, 10);
	}

	/**
	 * https://gforex.asia/tw/column/20160217 slippage: 首先讓我們來看什麼是滑點現象。
	 * 比如，在1美元=95‧20時下一個買單。 為了避免過大的虧損在94‧00日元的價格設定停損單。
	 * 如果價格一直下跌到94‧00日元理論上應該會自動平倉，結果卻以93.99日元的價格成交。 這種現象就叫做滑點。
	 * 原本應該只虧損1.2日元，因為滑點變成虧損了1.21日元。 0.01日元看起來金額很小，但是買的量越大，槓稈倍數越高，其金額就很客觀了。
	 * 對投資者而言，當然沒有滑點是最好的。
	 */
	public Order openMarket(String symbol, Op op, double volume, double price, int slippage) {
		print("Connected. Balance = " + getClient().AccountBalance());
		try {
			while (getClient().GetQuote(symbol) == null) {
				Thread.sleep(1);
			}
			OrderClient oc = new OrderClient(getClient());
			// 以bid价进行买入
			// OrderSend(String symbol, Op operation, double volume, double price, int
			// slippage, double stoploss, double takeprofit)
			Order order = oc.OrderSend(symbol, op, volume, price, 0, 0, 0);
			return order;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public Order closeMarket(Order order, double price) {
		return closeMarket(order, price, 10);
	}

	public Order closeMarket(Order order, double price, int slippage) {
		print("order ticket" + order.Ticket);
		OrderClient oc = new OrderClient(getClient());
		Order close;
		try {
			// OrderClose(String symbol, int ticket, double volume, double price, int
			// slippage)
			close = oc.OrderClose(order.Symbol, order.Ticket, order.Lots, price, 0);
			System.out.println("closed " + close.Profit);
			return order;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	// ======open market order end

	// ====== pending order start 挂单操作
	public Order pendingOrder(String symbol, Op op, double volume, double price, double slippage, double stoploss,
			double takeprofit) {
		try {
			OrderClient oc = new OrderClient(getClient());
			// OrderSend(String symbol, Op operation, double volume, double price, int
			// slippage, double stoploss, double takeprofit)
			Order order = oc.OrderSend(symbol, op, volume, price, (int) slippage, stoploss, takeprofit);
			System.out.println("ticket " + order.Ticket);
			return order;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public void asyncPendingOrder(String symbol, Op op, double volume, double price, int slippage, double stoploss,
			double takeprofit) throws InterruptedException, IOException, TimeoutException, ConnectException {
		OrderClient oc = new OrderClient(getClient());
		oc.OnOrderProgress.addListener(new OrderProgressEventHandler() {
			@Override
			public void invoke(Object o, OrderProgressEventArgs args) {
				print("TYPE = " + args.Type);
				if (args.Exception != null)
					print(args.Exception);
			}

		});
		oc.OrderSendAsync(symbol, op, volume, price, slippage, stoploss, takeprofit);
	}
	// ====== pending order end

	public void close() {
		getClient().Disconnect();
		ThreadPool.Executor.shutdown();
	}

	// ======= monitor any trading activity that happens on account start
	public void activity() throws IOException, TimeoutException, ConnectException {
		getClient().OnOrderUpdate.addListener(new OrderUpdateEventHandler() {

			@Override
			public void invoke(Object sender, OrderUpdateEventArgs update) {
				// TODO Auto-generated method stub
				print(update.Order.Type + " " + update.Order.Symbol + " " + update.Action);
			}

		});
	}
	// ======= monitor any trading activity that happens on account end

	// ====== Here we show how to get all opened orders. start
	public void openedOrders() throws IOException, TimeoutException, ConnectException, InterruptedException {
		while (true) {
			for (Order order : getClient().GetOpenedOrders()) {
				print(order.Ticket + " " + order.Symbol + " " + order.Type + " " + order.Profit);
			}
			Thread.sleep(2000);
		}
	}
	// ====== Here we show how to get all opened orders. end

	// From and To parameters it’s server time, not local time. ,时间为服务器的时间
	public void historyOrders() throws Exception {
		for (Order order : getClient().DownloadOrderHistory(LocalDateTime.now().minusDays(10),
				LocalDateTime.now().plusDays(1))) {
			print(order.Ticket + " " + order.Symbol + " " + order.Type);
		}
	}
	// From and To parameters it’s server time end

	// OHLC history start
	/**
	 * M1、M5、M15、M30、H1、H4、D1、W1、MN1
	 * @param dbList 
	 */
	public List<Record> historyQuotes(String symbol, Timeframe timeframe, LocalDateTime from, int count) {
		List<Record> listRecord = new ArrayList<>();
		List<String> saveRecord = new ArrayList<>();
		List<QuoteHistoryEventArgs> barRecord 	= new ArrayList<>();
		
		getClient().DownloadQuoteHistory(symbol, timeframe, from, (short) count);
		final CountDownLatch _catch = new CountDownLatch(1);
		getClient().OnQuoteHistory.addListener(new QuoteHistoryEventHandler() {
			@Override
			public void invoke(Object arg0, online.mtapi.mt4.QuoteHistoryEventArgs args) {
				// TODO Auto-generated method stub
				try {
					if (args.Bars.length == 1 && args.Bars[0].Time.getYear() == 1970) {
						PrintHelper.print("Symbol  = "+symbol+" : OVER?!!!!!! BarRecord = "+barRecord.size());
						if(barRecord.size()>0) {
							barRecord.parallelStream().forEach((eventArgs)->{
								String tableName		=	StatusHelper.getTableFromTimeFrame(eventArgs.TimeFrame.getValue());
								String tmpSymbol		=	eventArgs.Symbol;
								System.out.println("RUN!!!!! SQL");
								List<String> dbList		=	SqlHelper.getTimes(tableName,tmpSymbol,count);
								for (Bar bar : eventArgs.Bars) {
									long timeLong 		=	bar.Time.toEpochSecond(ZoneOffset.UTC);
									String timeStr		= 	TimeHelper.formatLocalDateTime(bar.Time);
									if(!dbList.contains(timeStr)) {
										String tmpSql	=	"insert ignore into `"+tableName+"` (`High`, `Low`, `Volume`, `Symbol`, `Close`, `Time`, `Open`) VALUE ("+bar.High+","+bar.Low+","+bar.Volume+",'"+tmpSymbol+"',"+bar.Close+",'"+timeStr+"',"+bar.Open+")";
										saveRecord.add(tmpSql);
										//saveRecord.add(new Record().set("Close", bar.Close).set("High", bar.High).set("Low", bar.Low).set("Open", bar.Open).set("Symbol", symbol).set("Time", timeStr).set("Volume", bar.Volume));
									}
									listRecord.add(new Record().set("Close", bar.Close).set("High", bar.High).set("Low", bar.Low).set("Open", bar.Open).set("Time", timeLong).set("Volume", bar.Volume));
								}
							});
							if(saveRecord.size()>0) {
								try {
									//Db.batch("insert ignore into `lj_quotes_m1` (`High`, `Low`, `Volume`, `Symbol`, `Close`, `Time`, `Open`)", paras, saveRecord.size());
									//Db.batchSave(StatusHelper.getTableFromTimeFrame(timeframe.getValue()), saveRecord,saveRecord.size());
									Db.batch(saveRecord, 500);
									//System.out.println(saveRecord);
									
								}catch(Exception e) {
									e.printStackTrace();
								}
							}
							PrintHelper.print(args.Bars.length + " AND " + listRecord.size()+" And SaveRecord = "+saveRecord.size());
						}
						_catch.countDown();
					} else {
						barRecord.add(args);
						//PrintHelper.print("Bar Size = "+args.Bars.length);
					}
				}catch(Exception e) {
					PrintHelper.print(e);
				}
			}
		});
		try {
			_catch.await();
		} catch (InterruptedException e) {
			PrintHelper.print(e);
			//e.printStackTrace();
			// TODO Auto-generated catch block
		}
		return listRecord;
	}
	
	/**
	 * @deprecated
	 */
	public String historyQuotesBk(String symbol, Timeframe timeframe, LocalDateTime from, int count) {
		// Record kv = new Record().set("json", "");
		List<Record> listRecord = new ArrayList<>();
		getClient().DownloadQuoteHistory(symbol, timeframe, from, (short) count);
		final CountDownLatch _catch = new CountDownLatch(1);
		getClient().OnQuoteHistory.addListener(new QuoteHistoryEventHandler() {
			@Override
			public void invoke(Object arg0, online.mtapi.mt4.QuoteHistoryEventArgs args) {
				// TODO Auto-generated method stub
				if (args.Bars.length == 1 && args.Bars[0].Time.getYear() == 1970) {
					_catch.countDown();
					return;
				} else {
					for (Bar bar : args.Bars) {
						listRecord.add(new Record().set("Close", bar.Close).set("High", bar.High).set("Low", bar.Low)
								.set("Open", bar.Open).set("Time", bar.Time.toEpochSecond(ZoneOffset.UTC))
								.set("Volume", bar.Volume));
					}
					if (listRecord.size() >= count) {
						_catch.countDown();
					}
					PrintHelper.print(args.Bars.length + " AND " + listRecord.size());

				}

			}
		});

		try {
			_catch.await();
		} catch (InterruptedException e) {
			PrintHelper.print(e);
			// TODO Auto-generated catch block
		}
		PrintHelper.print("OUT:" + listRecord.size());
		List<Record> newRecord = new ArrayList<>(listRecord);
		return JsonKit.toJson(new Record().set("Bars", newRecord));
	}
	// OHLC history end
	// save bar data to db
	public void saveHistoryQuotes(String tableName,String symbol, Timeframe timeframe, LocalDateTime from, int count) {
		final CountDownLatch _catch = new CountDownLatch(1);
		List<QuoteHistoryEventArgs> historyList=new ArrayList<>();
		List<String> cacheList=new ArrayList<>();
		List<String> saveRecord = new ArrayList<>();
		
		QuoteClient client =getClient();
		client.DownloadQuoteHistory(symbol, timeframe, from, (short) count);
		
		System.out.println("Start!");
		client.OnQuoteHistory.addListener(new QuoteHistoryEventHandler() {
			@Override
			public void invoke(Object arg0, online.mtapi.mt4.QuoteHistoryEventArgs args) {
				// TODO Auto-generated method stub
				try {
					if (args.Bars.length == 1 && args.Bars[0].Time.getYear() == 1970) {
						PrintHelper.print("Symbol  = "+symbol+"Size = "+historyList.size()+" : OVER?!!!!!!");
						
						if(historyList.size()>0) {
							historyList.forEach((quoteHistory)->{
								if(quoteHistory==null) {
									return;
								}
								String tmpSymbol=args.Symbol;
								for (Bar bar : quoteHistory.Bars) {
									String tmpTime	=	TimeHelper.formatLocalDateTime(bar.Time);
									String tmpKey	=	tmpTime+"_"+tmpSymbol;
									if(cacheList.contains(tmpKey)) {
										continue;
									}
									saveRecord.add("insert ignore into `"+tableName+"` (`High`, `Low`, `Volume`, `Symbol`, `Close`, `Time`, `Open`) VALUE ("+bar.High+","+bar.Low+","+bar.Volume+",'"+symbol+"',"+bar.Close+",'"+tmpTime+"',"+bar.Open+")");
									cacheList.add(tmpKey);
								}
								if(saveRecord.size()>0) {
									try {
										Db.batch(saveRecord, saveRecord.size());
									}catch(Exception e) {
										e.printStackTrace();
									}
									PrintHelper.print("Batch Insert:"+saveRecord.size()+ "  , Table= "+tableName+" And Symbol="+symbol);
								}
								
							});
						}
						_catch.countDown();
					} else {
						historyList.add(args);
						//PrintHelper.print("Symbol  Size = "+args.Bars.length+" Time="+args.TimeFrame);
					}
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
		});
		
		try {
			_catch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
			// TODO Auto-generated catch block
		}
		historyList.clear();
		System.out.println("OUT!");
	}
}
